/*
 * SPDX-License-Identifier: BSD-3-Clause
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2023. All rights reserved.
 * Description   : udk pci source file
 */

#include <stdlib.h>
#include <errno.h>

#include "udk_pci.h"

#define UDK_HEX 16
#ifndef UINT8_MAX
#define UINT8_MAX  (uint8_t)(~((uint8_t)0))    /* 0xFF       */
#define UINT32_MAX (uint32_t)(~((uint32_t)0))  /* 0xFFFFFFFF */
#endif

static inline const char *udk_get_pci_addr_field(const char *input, void *output, char end_flag)
{
    unsigned long val;
    char *end = NULL;

    if (input == NULL || *input == '\0' || *input == ' ' || *input == '-') {
        return NULL;
    }

    errno = 0; /* Record the error information */
    val = strtoul(input, &end, UDK_HEX);
    if (errno != 0 || *end != end_flag || val > (unsigned long)UINT8_MAX) {
        errno = errno ? errno : EINVAL;
        return NULL;
    }

    *(uint8_t *)output = (uint8_t)val;
    return end + 1;
}

static int udk_pci_bdf_parse(const char *str, struct udk_pci_addr *addr)
{
    const char *input = str;
    addr->domain = 0;

    input = udk_get_pci_addr_field(input, &addr->bus, ':');
    if (input == NULL) {
        return -EINVAL;
    }
    input = udk_get_pci_addr_field(input, &addr->devid, '.');
    if (input == NULL) {
        return -EINVAL;
    }
    input = udk_get_pci_addr_field(input, &addr->function, '\0');
    if (input == NULL) {
        return -EINVAL;
    }
    return 0;
}

static int udk_pci_dbdf_parse(const char *str, struct udk_pci_addr *addr)
{
    const char *input = str;
    unsigned long val;
    char *end = NULL;

    if (input == NULL || *input == '\0' || *input == ' ' || *input == '-') {
        return -EINVAL;
    }

    errno = 0;
    val = strtoul(str, &end, UDK_HEX);
    if (errno || *end != ':' || val > (unsigned long)UINT32_MAX) {
        return -EINVAL;
    }

    addr->domain = (uint32_t)val;
    input = end + 1;

    input = udk_get_pci_addr_field(input, &addr->bus, ':');
    if (input == NULL) {
        return -EINVAL;
    }
    input = udk_get_pci_addr_field(input, &addr->devid, '.');
    if (input == NULL) {
        return -EINVAL;
    }
    input = udk_get_pci_addr_field(input, &addr->function, '\0');
    if (input == NULL) {
        return -EINVAL;
    }

    return 0;
}

int udk_pci_addr_parse(const char *str, struct udk_pci_addr *addr)
{
    if (udk_pci_bdf_parse(str, addr) == 0 || udk_pci_dbdf_parse(str, addr) == 0) {
        return 0;
    }

    return -1;
}
